{
    This file isolates platform-specific routines which perform packing and
    unpacking of ValReal FP values during float <-> ASCII conversions.
    These routines, mostly, were gathered from various places of FPC RTL.

 ****************************************************************************
}
{
    Note about inlining: since unpack_float is used only once in str_real,
    it can be safely inlined; however pack_float is used several times in
    val_real, so its inlining does not seem practical, except of the case
    when this procedure simply calls the SoftFPU implementation.
}
// ---------------------------------------------------------------------
//
// single; format [MSB]: 1 sign bit, 8 bit exponent, 23 bit mantissa
//
// ---------------------------------------------------------------------
{$if defined(VALREAL_32) and not defined(VALREAL_PACK)}
{$if defined(fpc_softfpu_implementation)
     or ( defined(FPC_SYSTEM_HAS_extractFloat32Frac)
      and defined(FPC_SYSTEM_HAS_extractFloat32Exp)
      and defined(FPC_SYSTEM_HAS_extractFloat32Sign)
        )}

function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
begin
    unpack_float.f := extractFloat32Frac( float32( f ) );
    unpack_float.e := extractFloat32Exp( float32( f ) );
    minus := ( extractFloat32Sign( float32( f ) ) <> 0 );
end;

{$else not fpc_softfpu_implementation}

function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
type
    TSplitFloat = packed record
      case byte of
        0: ( f: ValReal );
        1: ( b: array [ 0 .. 3 ] of byte );
        2: ( w: array [ 0 .. 1 ] of word );
        3: ( d: dword );
    end;
var
    split: TSplitFloat;
begin
    split.f := f;
 {$ifdef endian_big}
    minus := ( split.b[0] and $80 <> 0 );
    unpack_float.e := ( split.w[0] shr 7 ) and $FF;
 {$else endian_little}
    minus := ( split.b[3] and $80 <> 0 );
    unpack_float.e := ( split.w[1] shr 7 ) and $FF;
 {$endif endian}
    unpack_float.f := split.d and $007FFFFF;
end;

{$endif fpc_softfpu_implementation}
{$endif unpack float32}

{$if defined(VALREAL_32) and defined(VALREAL_PACK)}
{$ifdef fpc_softfpu_implementation}

procedure pack_float( out f: ValReal; minus: boolean; exp: integer; const m: dword ); {$ifdef grisu1_inline}inline;{$endif}
begin
    f := float32rec( packFloat32( ord(minus), exp, m ) );
end;

{$else not fpc_softfpu_implementation}

procedure pack_float( out f: ValReal; minus: boolean; exp: integer; m: dword ); // {$ifdef grisu1_inline}inline;{$endif}
type
    TSplitFloat = packed record
      case byte of
        0: ( f: ValReal );
        1: ( b: array [ 0 .. 3 ] of byte );
        2: ( w: array [ 0 .. 1 ] of word );
        3: ( d: dword );
    end;
var
    split: TSplitFloat;
begin
    split.d := m;
 {$ifdef endian_big}
    split.w[0] := split.w[0] + ( exp and $FF ) shl 7;
    if minus then
        split.b[0] := split.b[0] or $80;
 {$else endian_little}
    split.w[1] := split.w[1] + ( exp and $FF ) shl 7;
    if minus then
        split.b[3] := split.b[3] or $80;
 {$endif endian}
    f := split.f;
end;

{$endif fpc_softfpu_implementation}
{$endif pack float32}

// ---------------------------------------------------------------------
//
// double; format [MSB]: 1 sign bit, 11 bit exponent, 52 bit mantissa
//
// ---------------------------------------------------------------------
{$if defined(VALREAL_64) and not defined(VALREAL_PACK)}
{$ifdef cpujvm}

function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
var
    doublebits: int64;
begin
    doublebits := JLDouble.doubleToLongBits( f );
    minus := ( doublebits < 0 );
    unpack_float.e := ( doublebits shr 52 ) and $7FF;
    unpack_float.f := ( doublebits and $000FFFFFFFFFFFFF );
end;

{$else not cpujvm}

{$if defined(fpc_softfpu_implementation)
     or ( defined(FPC_SYSTEM_HAS_extractFloat64Frac)
      and defined(FPC_SYSTEM_HAS_extractFloat64Exp)
      and defined(FPC_SYSTEM_HAS_extractFloat64Sign)
        )}

function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
begin
    unpack_float.f := extractFloat64Frac( float64( f ) );
    unpack_float.e := extractFloat64Exp( float64( f ) );
    minus := ( extractFloat64Sign( float64( f ) ) <> 0 );
end;

{$else not fpc_softfpu_implementation}

function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
type
    TSplitFloat = packed record
      case byte of
        0: ( f: ValReal );
        1: ( b: array [ 0 .. 7 ] of byte );
        2: ( w: array [ 0 .. 3 ] of word );
        3: ( d: array [ 0 .. 1 ] of dword );
        4: ( l: qword );
    end;
var
    doublebits: TSplitFloat;
begin
  {$ifdef FPC_DOUBLE_HILO_SWAPPED}
    // high and low dword are swapped when using the arm fpa
    doublebits.d[0] := TSplitFloat(f).d[1];
    doublebits.d[1] := TSplitFloat(f).d[0];
  {$else not FPC_DOUBLE_HILO_SWAPPED}
    doublebits.f := f;
  {$endif FPC_DOUBLE_HILO_SWAPPED}
  {$ifdef endian_big}
    minus := ( doublebits.b[0] and $80 <>0 );
    unpack_float.e := ( doublebits.w[0] shr 4 ) and $7FF;
  {$else endian_little}
    minus := ( doublebits.b[7] and $80 <> 0 );
    unpack_float.e := ( doublebits.w[3] shr 4 ) and $7FF;
  {$endif endian}
    unpack_float.f := doublebits.l and $000FFFFFFFFFFFFF;
end;

{$endif fpc_softfpu_implementation}
{$endif cpujvm}
{$endif unpack float64}

{$if defined(VALREAL_64) and defined(VALREAL_PACK)}
{$ifdef cpujvm}

procedure pack_float( out f: ValReal; minus: boolean; exp: integer; const m: qword ); {$ifdef grisu1_inline}inline;{$endif}
var
    doublebits: int64;
begin
    doublebits := ( m and $000FFFFFFFFFFFFF ) + ( qword( exp and $7FF ) shl 52 ) + ( qword( ord(minus) ) shl 63 );
    f := JLDouble.longBitsToDouble( doublebits );
end;

{$else not cpujvm}

{$ifdef fpc_softfpu_implementation}

procedure pack_float( out f: ValReal; minus: boolean; exp: integer; const m: qword ); {$ifdef grisu1_inline}inline;{$endif}
begin
    f := packFloat64( ord(minus), exp, m );
end;

{$else not fpc_softfpu_implementation}

procedure pack_float( out f: ValReal; minus: boolean; exp: integer; const m: qword ); // {$ifdef grisu1_inline}inline;{$endif}
type
    TSplitFloat = packed record
      case byte of
        0: ( f: ValReal );
        1: ( b: array [ 0 .. 7 ] of byte );
        2: ( w: array [ 0 .. 3 ] of word );
        3: ( d: array [ 0 .. 1 ] of dword );
        4: ( l: qword );
    end;
var
    doublebits: TSplitFloat;
begin
    doublebits.l := m;
  {$ifdef endian_big}
    doublebits.w[0] := doublebits.w[0] + ( exp and $7FF ) shl 4;
    if minus then
        doublebits.b[0] := doublebits.b[0] or $80;
  {$else endian_little}
    doublebits.w[3] := doublebits.w[3] + ( exp and $7FF ) shl 4;
    if minus then
        doublebits.b[7] := doublebits.b[7] or $80;
  {$endif endian}
  {$ifdef FPC_DOUBLE_HILO_SWAPPED}
    // high and low dword are swapped when using the arm fpa
    TSplitFloat(f).d[1] := doublebits.d[0];
    TSplitFloat(f).d[0] := doublebits.d[1];
  {$else not FPC_DOUBLE_HILO_SWAPPED}
    f := doublebits.f;
  {$endif FPC_DOUBLE_HILO_SWAPPED}
end;

{$endif fpc_softfpu_implementation}
{$endif cpujvm}
{$endif pack float64}

// ---------------------------------------------------------------------
//
// extended; format [MSB]: 1 Sign bit, 15 bit exponent, 64 bit mantissa (explicit integer bit is included)
//
// ---------------------------------------------------------------------
{$if defined(VALREAL_80) and not defined(VALREAL_PACK)}
{$ifdef fpc_softfpu_implementation}

function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
begin
    unpack_float.fh := 0;
    unpack_float.f := extractFloatx80Frac( f );
    unpack_float.e := extractFloatx80Exp( f );
    minus := ( extractFloatx80Sign( f ) <> 0 );
end;

{$else not fpc_softfpu_implementation}

function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
type
    TSplitFloat = packed record
      case byte of
        0: ( f: ValReal );
        1: ( b: array [ 0 .. 9 ] of byte );
        2: ( l: qword; e: word )
    end;
var
    split: TSplitFloat;
begin
    split.f := f;
  {$ifdef endian_big}
    {$error Big endian extended double [80-bit] is not implemented}
  {$else endian_little}
    minus := ( split.b[9] and $80 <> 0 );
    unpack_float.e := split.e and $7FFF;
    unpack_float.f := split.l;
    unpack_float.fh := 0;
  {$endif endian}
end;

{$endif fpc_softfpu_implementation}
{$endif unpack floatx80}

{$if defined(VALREAL_80) and defined(VALREAL_PACK)}
{$ifdef fpc_softfpu_implementation}

procedure pack_float( out f: ValReal; minus: boolean; exp: integer; const m: qword ); {$ifdef grisu1_inline}inline;{$endif}
begin
    f := packFloatx80( ord(minus), exp, m );
end;

{$else not fpc_softfpu_implementation}

procedure pack_float( out f: ValReal; minus: boolean; exp: integer; const m: qword ); // {$ifdef grisu1_inline}inline;{$endif}
type
    TSplitFloat = packed record
      case byte of
        0: ( f: ValReal );
        1: ( b: array [ 0 .. 9 ] of byte );
        2: ( l: qword; e: word )
    end;
var
    split: TSplitFloat;
begin
  {$ifdef endian_big}
    {$error Big endian extended double [80-bit] is not implemented}
  {$else endian_little}
    split.l := m;
    split.e := exp and $7FFF;
    if minus then
        split.b[9] := split.b[9] or $80;
  {$endif endian}
    f := split.f;
end;

{$endif fpc_softfpu_implementation}
{$endif pack floatx80}

// ---------------------------------------------------------------------
//
// float128; format [MSB]: 1 Sign bit, 15 bit exponent, 112 bit mantissa
//
// ---------------------------------------------------------------------
{$if defined(VALREAL_128) and not defined(VALREAL_PACK)}
{$ifdef fpc_softfpu_implementation}

function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
begin
    unpack_float.fh := extractFloat128Frac0( f );
    unpack_float.f := extractFloat128Frac1( f );
    unpack_float.e := extractFloat128Exp( f );
    minus := ( extractFloat128Sign( f ) <> 0 );
end;

{$else not fpc_softfpu_implementation}

function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
type
    TSplitFloat = packed record
      case byte of
        0: ( f: ValReal );
        1: ( b: array [ 0 .. 15 ] of byte );
        2: ( w: array [ 0 .. 7 ] of word );
        3: ( l: array [ 0 .. 1 ] of qword );
    end;
var
    split: TSplitFloat;
begin
    split.f := f;
  {$ifdef endian_big}
    {$error Big endian long double [128-bit] is not implemented}
  {$else endian_little}
    minus := ( split.b[15] and $80 <> 0 );
    unpack_float.e := split.w[7] and $7FFF;
    unpack_float.f := split.l[0];
    unpack_float.fh := split.l[1] and $0000FFFFFFFFFFFF;
  {$endif endian}
end;

{$endif fpc_softfpu_implementation}
{$endif unpack float128}

{$if defined(VALREAL_128) and defined(VALREAL_PACK)}
{$ifdef fpc_softfpu_implementation}

procedure pack_float( out f: ValReal; minus: boolean; exp: integer; const h, l: qword ); {$ifdef grisu1_inline}inline;{$endif}
begin
    f := packFloat128( ord(minus), exp, h, l );
end;

{$else not fpc_softfpu_implementation}

procedure pack_float( out f: ValReal; minus: boolean; exp: integer; const h, l: qword ); // {$ifdef grisu1_inline}inline;{$endif}
type
    TSplitFloat = packed record
      case byte of
        0: ( f: ValReal );
        1: ( b: array [ 0 .. 15 ] of byte );
        2: ( w: array [ 0 .. 7 ] of word );
        3: ( l: array [ 0 .. 1 ] of qword );
    end;
var
    split: TSplitFloat;
begin
  {$ifdef endian_big}
    {$error Big endian long double [128-bit] is not implemented}
  {$else endian_little}
    split.l[0] := l;
    split.l[1] := h;
    split.w[7] := exp and $7FFF;
    if minus then
        split.b[15] := split.b[15] or $80;
  {$endif endian}
    f := split.f;
end;

{$endif fpc_softfpu_implementation}
{$endif pack float128}
